#include <os/process.h>
#include <os/task.h>
#include <os/timer.h>
#include <os/schedule.h>
#include <os/pthread.h>
#include <os/debug.h>
#include <os/safety.h>
#include <arch/task.h>
#include <os/waitqueue.h>
#include <sys/wait.h>
#include <lib/errno.h>

void PthreadDescriptInit(pthread_desc_t *pthread)
{
    if (pthread != NULL)
    {
        AtomicSet(&pthread->thread_count, 0);
    }
}

void PthreadDescriptExit(pthread_desc_t *pthread)
{
    if (pthread != NULL)
    {
        KMemFree(pthread);
    }
}

void PthreadEntry(void *arg)
{
    trap_frame_t *frame = TASK_GET_TRAP_FRAME(cur_task);
    KernelSwitchToUser(frame);
}

//create pthread
task_t *PthreadStart(task_func_t *fun, void *arg, pthread_attr_t *attr, void *thread_entry)
{
    task_t *parent = cur_task;
    task_t *task = NULL;
    trap_frame_t *frame = NULL;

    //check parent task pthread descript
    if (!parent->pthread)
    {
        if (ProcessThreadInit(parent))
        {
            //process thread descript init failed
            return NULL;
        }
    }
    //create pthread task
    task = (task_t *)KMemAlloc(sizeof(task_t));
    if (!task)
        return NULL;
    TaskInit(task, "pthread", TASK_PRIOR_LEVEL_NORMAL);
    //parent task is process
    if (parent->threadgroup_id == parent->pid)
    {
        //task threadgroup id=parent task pid
        task->threadgroup_id = parent->pid;
    }
    else
    { //parent task is pthread
        //task pthread thread group id=parent pthread thread group id
        task->threadgroup_id = parent->threadgroup_id;
    }
    //set pthread info
    task->parent_pid = parent->pid;
    task->pthread = parent->pthread;
    task->processgroup_id = parent->processgroup_id;
    task->vmm = parent->vmm;
    task->fileman = parent->fileman;
    ExceptionManagerInit(&task->exception_manager);

    //frame init and build
    ProcessTrapFrameInit(task);
    TaskStackBuild(task, PthreadEntry, arg);
    frame = TASK_GET_TRAP_FRAME(task);
    UserThreadFrameBuild(frame, arg, fun, (void *)thread_entry, (uint8_t *)attr->stackaddr + attr->stacksize);

    //if assign detach status,set flags
    if (attr->detachstatus == PTHREAD_CREATE_DETACHED)
    {
        task->flags |= THREAD_DETACH;
    }
    //pthread count++
    AtomicInc(&task->pthread->thread_count);
    //check pthread count if overflow
    if (AtomicGet(&task->pthread->thread_count) > PTHREAD_NUM_MAX)
    {
        //free thread task
        AtomicDec(&task->pthread->thread_count);
        KMemFree(task); //free task
        return NULL;
    }
    //add task to global list and schedule queue
    TaskAddToGlobalList(task);
    SchedQueueAddTaskTail(SchedGetCurUnit(), task);
    return task;
}

void PthreadExit(int status)
{
    task_t *cur = cur_task;
    task_t *parent;

    AtomicDec(&cur->pthread->thread_count);
    //no other thread
    if (!AtomicGet(&cur->pthread->thread_count))
    {
        KPrint(PRINT_DEBUG "PthreadExit: pid=%x no other threads,exit process!\n", cur->pid);
        SysExit(status);
    }
    cur->exit_status = status;
    TaskCancelTimer(cur);
    TaskExitHook(cur);
    //thread detch status
    if (cur->flags & THREAD_DETACH)
    {
        //cur thread was waitting by other thread
        if (cur->flags & THREAD_JOINED)
        {
            parent = TaskFindByPid(cur->parent_pid);
            //parent task is waitting
            if (parent != NULL && parent->status == TASK_WAITTING)
            {
                if (parent->threadgroup_id == cur->parent_pid)
                {
                    //unblock parent task and clear parent flags
                    parent->flags &= ~THREAD_JOINNING;
                    TaskUnBlock(parent);
                }
            }
        }
        cur->parent_pid = USER_INIT_PROCESS_ID;
    }
    //thread no detach status
    //wait parent thread recover
    parent = TaskFindByPid(cur->parent_pid);
    if (parent)
    {
        if (parent->status == TASK_WAITTING)
        {
            TaskUnBlock(parent);
            TaskBlock(TASK_HANGING);
        }
        else
        {
            TaskBlock(TASK_ZOMBIE);
        }
    }
    else
    {
        TaskBlock(TASK_ZOMBIE);
    }
}

int PthreadJoin(pthread_t thread, void **thread_return)
{
    task_t *waiter = cur_task;
    task_t *task, *find;
    int pid, status;

    //find targe pthread
    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        if (task->pid == thread && task->status != TASK_ZOMBIE)
        {
            find = task;
            break;
        }
    }

    if (!find)
        return -1;
    //had been detach
    if (find->flags & THREAD_DETACH)
        return -1;
    if (find->flags & THREAD_JOINED)
        return -1;

    //set flags
    find->flags |= THREAD_JOINED;
    find->parent_pid = waiter->pid;
    waiter->flags |= THREAD_JOINNING;

    //current thread into waitting
    do
    {
        status = 0;
        pid = WaitOneHanggingThread(waiter, find->pid, &status);
        if (pid == thread)
            break;
        if (!(waiter->flags & THREAD_JOINNING))
            break;
        TaskBlock(TASK_WAITTING); //block and wait be wakeup
    } while (pid == -1);

    if (thread_return)
    {
        if (MemCopyToUser(thread_return, &status, sizeof(void *)))
            return -1;
    }
    return 0;
}

int WaitOneHanggingThread(task_t *parent,pid_t pid,int *status)
{
    task_t *child,*next;
    list_traversal_all_owner_to_next_safe(child,next,&parent->list,list)
    {
        //find child thread
        if(child->pid==pid)
        {
            if(child->status==TASK_HANGING)
            {
                if(status)
                    *status=child->exit_status;
                //exit and return pid
                ProcessDestroy(child,1);
                return pid;
            }
        }
    }
    return -1;
}


int SysThreadDetach(pid_t thread)
{
    task_t *task = TaskFindByPid(thread);
    task_t *parent;

    if (!task)
        return -1;
    KPrint(PRINT_DEBUG "SysThreadDetach: thread=%s detach", task->name);
    task->flags |= THREAD_DETACH;
    //ative parent task if present
    if (task->flags & THREAD_JOINED)
    {
        parent = TaskFindByPid(task->parent_pid);
        if (parent != NULL && parent->status == TASK_WAITTING)
        {
            if (parent->threadgroup_id == task->threadgroup_id)
            {
                parent->flags &= ~THREAD_JOINNING;
                TaskUnBlock(parent);
            }
        }
    }
    return 0;
}

pid_t SysThreadCreate(pthread_attr_t *attr, task_func_t *func, void *arg, void *thread_entry)
{
    task_t *task = NULL;

    if (!attr || !func || !thread_entry)
        return -EINVAL;

    if (SafetyCheckRange(func, sizeof(task_func_t *) < 0) || SafetyCheckRange(thread_entry, sizeof(void *) < 0))
    {
        return -EINVAL;
    }
    //start pthread
    task = PthreadStart(func, arg, attr, thread_entry);
    if (!task)
        return -EINVAL;
    return task->pid;
}

void SysThreadExit(int status)
{
    PthreadExit(status);
}

int SysThreadCancel(pthread_t thread)
{
    task_t *task;
    task = TaskFindByPid(thread);
    if (!task)
        return -1;
    task->flags |= THREAD_CANCELED;
    if (task->flags & THREAD_CANCEL_ASYCHRONOUS)
    {
        if (task == cur_task) //exit thread
        {
            PthreadExit(THREAD_CANCEL_ASYCHRONOUS);
        }
        else
        {
            ProcessCloseOneThread(task); //close thread on process
        }
    }
}

void SysThreadTestCancel()
{
    TASK_CHECK_THREAD_CANNELATION(cur_task);
}

int SysThreadSetCancelStatus(int status, int *oldstatus)
{
    task_t *cur = cur_task;

    if (status > 2)
        return -1;

    if (oldstatus)
    {
        //disable cancel
        if (cur->flags & THREAD_CANCEL_DISABLE)
        {
            int _oldstatus = PTHREAD_CANCEL_DISABLE;
            if (MemCopyToUser(oldstatus, &_oldstatus, sizeof(int)) < 0)
            {
                return -1;
            }
        }
        else
        {
            //enable cancel
            int _oldstatus = PTHREAD_CANCEL_ENABLE;
            if (MemCopyToUser(oldstatus, &_oldstatus, sizeof(int)) < 0)
            {
                return -1;
            }
        }
    }
    //set thread cancel enable
    if (status == PTHREAD_CANCEL_DISABLE)
        cur->flags |= THREAD_CANCEL_DISABLE;
    else
        cur->flags |= THREAD_CANCEL_ENABLE;
    return 0;
}

int SysThreadSetCancelType(int type, int *oldtype)
{
    task_t *cur = cur_task;
    if (type < 2)
        return -1;
    if (oldtype)
    {
        if (cur->flags & THREAD_CANCEL_ASYCHRONOUS)
        {
            int _type = PTHRED_CANCEL_ASYNHRONOUS;

            if (MemCopyToUser(oldtype, &_type, sizeof(int)))
                return -1;
        }
        else
        {
            int _type = PTHREAD_CANCEL_DEFFERED;
            if (MemCopyToUser(oldtype, &_type, sizeof(int)))
                return -1;
        }
    }
    if (type == PTHRED_CANCEL_ASYNHRONOUS)
        cur->flags |= THREAD_CANCEL_ASYCHRONOUS;
    else
        cur->flags &= ~THREAD_CANCEL_ASYCHRONOUS;
}

int SysThreadJoin(pthread_t thread, void **thread_return)
{
    TASK_CHECK_THREAD_CANNELATION(cur_task);
    return PthreadJoin(thread, thread_return);
}
